Building an Extendable Application

In the sections that follow, I will take you through a complete example that illustrates the process of building an extendable Windows Forms application that can be augmented by the functionality of external assemblies. What I will not do at this point is comment on the process of building GUI applications with this tried and true desktop API (see Appendix A for an overview of the Windows Forms API).

Note Windows Forms was the initial desktop API of the .NET platform. However, since the release of .NET 3.0, the Windows Presentation Foundation (WPF) API is quickly becoming preferred GUI framework. While this is true, I will make use of Windows Forms for a number of client UI examples in this text, as the related code is a tad bit more intuitive than the corresponding WPF code.

If you are not familiar with the process of building Windows Forms applications, feel free to simply open up the supplied sample code and follow along. To serve as a road map, our extendable application entails the following assemblies:

Again, this application will make use of dynamic loading, reflection, and late binding to dynamically gain the functionality of assemblies it has no prior knowledge of.

Building CommonSnappableTypes.dll

The first order of business is to create an assembly that contains the types that a given snap-in must leverage to be plugged into the expandable Windows Forms application. The CommonSnappableTypes Class Library project defines two types:

namespace CommonSnappableTypes
{
    public interface IAppFunctionality
    {
        void DoIt();
    }

    [AttributeUsage(AttributeTargets.Class)]
    public sealed class CompanyInfoAttribute : System.Attribute
    {
        public string CompanyName { get; set; }
        public string CompanyUrl { get; set; }
    }
}

The IAppFunctionality interface provides a polymorphic interface for all snap-ins that can be consumed by the extendable Windows Forms application. Given that this example is purely illustrative, you supply a single method named DoIt(). A more realistic interface (or a set of interfaces) might allow the object to generate scripting code, render an image onto the application’s toolbox, or integrate into the main menu of the hosting application.

The CompanyInfoAttribute type is a custom attribute that can be applied on any class type that wishes to be snapped into the container. As you can tell by the definition of this class, [CompanyInfo] allows the developer of the snap-in to provide some basic details about the component’s point of origin.

Building the C# Snap-In

Next up, you need to create a type that implements the IAppFunctionality interface. Again, to focus on the overall design of an extendable application, a trivial type is in order. Assume a new C# Class Library project named CSharpSnapIn defines a class type named CSharpModule. Given that this class must make use of the types defined in CommonSnappableTypes, be sure to set a reference to the CommpnSnappableTypes assembly (as well as System.Windows.Forms.dll to display a noteworthy message). This being said, here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using CommonSnappableTypes;
using System.Windows.Forms;

namespace CSharpSnapIn
{
    [CompanyInfo(CompanyName = "My Company",
        CompanyUrl = "www.MyCompany.com")]
    public class CSharpModule : IAppFunctionality
    {
        void IAppFunctionality.DoIt()
        {
            MessageBox.Show("You have just used the C# snap in!");
        }
    }
}

Notice that I choose to make use of explicit interface implementation (see Chapter 9) when supporting the IAppFunctionality interface. This is not required; however, the idea is that the only part of the system that needs to directly interact with this interface type is the hosting Windows application. By explicitly implementing this interface, the DoIt() method is not directly exposed from the CSharpModule type.

Building the Visual Basic Snap-In

Now, to simulate the role of a third-party vendor who prefers Visual Basic over C#, create a new Visual Basic Class Library (VbSnapIn) that references the same external assemblies as the previous CSharpSnapIn project.

Note By default, a Visual Basic project will not display the References folder within the Solution Explorer. To add references in a VB project, use the Project | Add Reference… menu option of Visual Studio 2010.

The code is (again) intentionally simple:

Imports System.Windows.Forms
Imports CommonSnappableTypes

<CompanyInfo(CompanyName:="Chucky's Software", CompanyUrl:="www.ChuckySoft.com")>
Public Class VbSnapIn
	Implements IAppFunctionality

	Public Sub DoIt() Implements CommonSnappableTypes.IAppFunctionality.DoIt
		MessageBox.Show("You have just used the VB snap in!")
	End Sub
End Class

Notice that applying attributes in the syntax of Visual Basic requires angle brackets (< >) rather than square brackets ([ ]). Also notice that the Implements keyword is used to implement interface types on a given class or structure.

Building an Extendable Windows Forms Application

The final step is to create a new C# Windows Forms application (MyExtendableApp) that allows the user to select a snap-in using a standard Windows Open dialog box. If you have not created a Windows Forms application before, begin this final project of the chapter by selecting a Windows Forms Application project from the New Project dialog box of Visual Studio 2010 (Figure 15-7).

Figure 15-7

Figure 15-7 Creating a new Windows Forms project with Visual Studio 2010

Now, set a reference to the CommonSnappableTypes.dll assembly, but not the CSharpSnapIn.dll or VbSnapIn.dll code libraries. As well, import the System.Reflection and CommonSnappableTypes namespaces into your form’s primary code file (which you can open by right clicking on the form designer and selecting View Code. Remember that the whole goal of this application is to make use of late binding and reflection to determine the “snapability” of independent binaries created by third-party vendors.

Again, I won’t bother to examine all the details of Windows Forms development at this point in the text (see Appendix A). However, assuming you have placed a MenuStrip component onto the forms designer, define a topmost menu item named File that provides a single submenu named Snap In Module. As well, the main window will contain a ListBox type (which I renamed as lstLoadedSnapIns) that will be used to display the names of each snap-in loaded by the user. Figure 15-8 shows the final GUI.

Figure 15-8

Figure 15-8 GUI for MyExtendableApp

The code that handles the Click event for the File > Snap In Module menu item (which may be created simply by double-clicking the menu item from the design-time editor) displays a File Open dialog box and extracts the path to the selected file. Assuming the user did not select the CommonSnappableTypes.dll assembly (as this is purely infrastructure), the path is then sent into a helper function named LoadExternalModule() for processing (implemented next). This method will return false when it is unable to find a class implementing IAppFunctionality:

private void snapInModuleToolStripMenuItem_Click(object sender,
EventArgs e)
{
    // Allow user to select an assembly to load.
    OpenFileDialog dlg = new OpenFileDialog();

    if (dlg.ShowDialog() == DialogResult.OK)
    {
        if(dlg.FileName.Contains("CommonSnappableTypes"))
            MessageBox.Show("CommonSnappableTypes has no snap-ins!");
        else if(!LoadExternalModule(dlg.FileName))
            MessageBox.Show("Nothing implements IAppFunctionality!");
    }
}

The LoadExternalModule() method performs the following tasks:

If a type implementing IAppFunctionality is found, the DoIt() method is called, and the fully qualified name of the type is added to the ListBox (note that the foreach loop will iterate over all types in the assembly to account for the possibility that a single assembly has multiple snap-ins).

private bool LoadExternalModule(string path)
{
    bool foundSnapIn = false;
    Assembly theSnapInAsm = null;

    try
    {
        // Dynamically load the selected assembly.
        theSnapInAsm = Assembly.LoadFrom(path);
    }
    catch(Exception ex)
    {
        MessageBox.Show(ex.Message);
        return foundSnapIn;
    }

    // Get all IAppFunctionality compatible classes in assembly.
    var theClassTypes = from t in theSnapInAsm.GetTypes()
        where t.IsClass &&
        (t.GetInterface("IAppFunctionality") != null)
        select t;

    // Now, create the object and call DoIt() method.
    foreach (Type t in theClassTypes)
    {
        foundSnapIn = true;
        // Use late binding to create the type.
        IAppFunctionality itfApp =
            (IAppFunctionality)theSnapInAsm.CreateInstance(t.FullName, true);
        itfApp.DoIt();
        lstLoadedSnapIns.Items.Add(t.FullName);
    }
    return foundSnapIn;
}

At this point, you can run your application. When you select the CSharpSnapIn.dll or VbSnapIn.dll assemblies, you should see the correct message displayed. The final task is to display the metadata provided by the [CompanyInfo] attribute. To do so, update LoadExternalModule() to call a new helper function named DisplayCompanyData() before exiting the foreach scope. Notice this method takes a single System.Type parameter.

private bool LoadExternalModule(string path)
{
...
    foreach (Type t in theClassTypes)
    {
...
        // Show company info.
        DisplayCompanyData(t);
    }
    return foundSnapIn;
}

Using the incoming type, simply reflect over the [CompanyInfo] attribute:

private void DisplayCompanyData(Type t)
{
    // Get [CompanyInfo] data.
    var compInfo = from ci in t.GetCustomAttributes(false) where
        (ci.GetType() == typeof(CompanyInfoAttribute))
        select ci;

    // Show data.
    foreach (CompanyInfoAttribute c in compInfo)
    {
        MessageBox.Show(c.CompanyUrl,
            string.Format("More info about {0} can be found at", c.CompanyName));
    }
}

Figure 15-9 shows one possible run.Figure 15-9 shows one possible run.

Figure 15-9

Figure 15-9 Snapping in external assemblies

Excellent! That wraps up the example application. I hope you can see that the topics presented in this chapter can be quite helpful in the real world and are not limited to the tool builders of the world.

Source Code The ExtendableApp folder under the Chapter 15 subdirectory contains the CommonSnappableTypes, CSharpSnapIn, VbSnapIn, and MyExtendableApp projects.